﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Red.Controls
{
    /// <summary>
    ///     Multi-line text box with terminal-like functionality
    /// </summary>
    public class TerminalBox : TextBox
    {
        #region Data

        private AutoCompletion _autoCompletion = new AutoCompletion();
        private List<string> _history = new List<string>();
        private int _historyCursor = -1;
        private int _curLineStartIndex = -1;
        private bool _inputBlocked = true;

        #endregion Data

        #region Properties

        public string Prompt { get; set; }

        #endregion Properties

        #region C-tor

        public TerminalBox()
        {
            Prompt = ">> ";
            IsUndoEnabled = false;
            AcceptsReturn = false;
            AcceptsTab = false;
            ContextMenu = null;
            Clear();
        }

        #endregion C-tor

        #region Public methods

        /// <summary>
        ///     Enables textual input
        /// </summary>
        public void ShowPrompt()
        {
            Text += "\n" + Prompt;
            CaretIndex = Text.Length;
            _curLineStartIndex = Text.Length;
            _autoCompletion.SetTextBase( _curLineStartIndex );
            EnableInput( true );
        }

        /// <summary>
        ///     Inserts a text at the end of the Terminal content. Note that it will block the input until you call ShowPrompt.
        /// </summary>
        public void AddLine( string line, bool newline = true )
        {
            Text += line;
            if ( newline )
                Text += "\n";
            EnableInput( false );
        }

        /// <summary>
        ///     Removes all the content
        /// </summary>
        public new void Clear()
        {
            base.Clear();
            _curLineStartIndex = -1;
        }

        /// <summary>
        ///     Simulates entering a text after the prompt and pressing Enter
        /// </summary>
        public void RunCommand( string cmd )
        {
            if ( _inputBlocked )
            {
                ShowPrompt();
            }
            SimulateInput( cmd );
            Enter();
        }

        /// <summary>
        ///     Saves the history (up to maxHistoryCount latest entries) to an array.
        /// </summary>
        public string[] SaveHistory( int maxHistoryCount )
        {
            int numElements = Math.Min( _history.Count, maxHistoryCount );
            int startIdx = _history.Count - numElements;
            string[] result = new string[numElements];
            for ( int i = 0; i < numElements; ++i )
            {
                result[i] = _history[i + startIdx];
            }
            return result;
        }

        /// <summary>
        ///     Resets the history from an array.
        /// </summary>
        public void LoadHistory( string[] history )
        {
            _history = new List<string>( history );
            _historyCursor = _history.Count;
        }

        /// <summary>
        /// Sets available commands in replenishment system
        /// </summary>
        /// <param name="availableCommands">Collection of available commands for current interop root object</param>
        public void SetAvailableCommands( string[] availableCommands )
        {
            _autoCompletion.AvailableCommands = availableCommands;
        }

        #endregion Public methods

        public delegate void LineEnteredHandler( TerminalBox sender, string line );
        public event LineEnteredHandler LineEntered;

        #region Event handlers

        protected override void OnPreviewKeyDown( KeyEventArgs e )
        {
            if ( _inputBlocked )
            {
                e.Handled = true;
                return;
            }

            switch ( e.Key )
            {
                case Key.Up:
                    HistoryBack();
                    e.Handled = true;
                    break;
                case Key.Down:
                    HistoryForward();
                    e.Handled = true;
                    break;
                case Key.Delete:
                    if ( CaretIndex < _curLineStartIndex )
                        e.Handled = true;
                    break;
                case Key.Back:
                    if ( CaretIndex <= _curLineStartIndex )
                        e.Handled = true;
                    break;
                case Key.Enter:
                    Enter();
                    e.Handled = true;
                    break;
                case Key.Left:
                    if ( CaretIndex <= _curLineStartIndex )
                    {
                        CaretIndex = _curLineStartIndex;
                        e.Handled = true;
                    }
                    break;
                case Key.Right:
                    if ( CaretIndex < _curLineStartIndex )
                    {
                        CaretIndex = _curLineStartIndex;
                        e.Handled = true;
                    }
                    break;
                case Key.Home:
                    if ( Keyboard.Modifiers == ModifierKeys.Shift )
                        Select( _curLineStartIndex, CaretIndex - _curLineStartIndex );
                    else
                        CaretIndex = _curLineStartIndex;
                    e.Handled = true;
                    break;
                case Key.End:
                    if ( Keyboard.Modifiers == ModifierKeys.Shift )
                        Select( CaretIndex, Text.Length - CaretIndex );
                    else
                        CaretIndex = Text.Length;
                    e.Handled = true;
                    break;
                case Key.PageUp: // the case is here to block selection (+shift)
                    PageUp();
                    e.Handled = true;
                    break;
                case Key.PageDown:
                    PageDown();
                    e.Handled = true;
                    break;
                case Key.V:
                    if ( Keyboard.Modifiers == ModifierKeys.Control )
                    { // "paste"
                        if ( CaretIndex < _curLineStartIndex )
                            CaretIndex = Text.Length;
                    }
                    break;
                case Key.Tab:
                    {
                        string finalLine = _autoCompletion.Replace( Text );
                        SimulateInput( finalLine );
                        e.Handled = true;
                        break;
                    }
            }

            if ( e.Handled == false )
            {
                _autoCompletion.SetUserCursor( CaretIndex + 1 );
                _autoCompletion.RecreatePossibleCommands();
            }

            base.OnPreviewKeyDown( e );
        }

        protected override void OnTextChanged( TextChangedEventArgs e )
        {
            // keeping caret at the end should be plugged into this event to prevent from visible scrollbar skipping
            if ( CaretIndex < _curLineStartIndex )
                CaretIndex = Text.Length;
            base.OnTextChanged( e );
        }

        protected override void OnTextInput( TextCompositionEventArgs e )
        {
            // this one is called before OnTextChanged and is also needed to prevent typing anything before the prompt
            if ( CaretIndex < _curLineStartIndex )
                CaretIndex = Text.Length;
            base.OnTextInput( e );
        }

        #endregion Event handlers

        #region Private methods

        private void EnableInput( bool enable )
        {
            if ( enable )
            {
                CaretBrush = null; // use default
                _inputBlocked = false;
            }
            else
            {
                CaretBrush = new SolidColorBrush( Color.FromArgb( 0, 0, 0, 0 ) );
                _inputBlocked = true;
            }
        }

        private void SimulateInput( string text )
        {
            Debug.Assert( !_inputBlocked );
            Text = Text.Substring( 0, _curLineStartIndex ) + text;
            CaretIndex = Text.Length;
        }

        private void HistoryBack()
        {
            Debug.Assert( !_inputBlocked );
            if ( _historyCursor > 0 )
            {
                --_historyCursor;
                SimulateInput( _history[_historyCursor] );
            }
        }

        private void HistoryForward()
        {
            Debug.Assert( !_inputBlocked );
            if ( _historyCursor < _history.Count - 1 )
            {
                ++_historyCursor;
                SimulateInput( _history[_historyCursor] );
            }
        }

        private void Enter()
        {
            string line = Text.Substring( _curLineStartIndex );
            if ( !String.IsNullOrEmpty( line ) )
            {
                EnableInput( false );
                if ( _history.Count == 0 || _history.Last() != line )
                {
                    _history.Add( line );
                }
                _historyCursor = _history.Count;
                Text += "\n";
                if ( LineEntered != null )
                    LineEntered( this, line );
            }
            else
            {
                ShowPrompt();
            }
        }

        #endregion Private methods
    }
}
